﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Pay.Core;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;

namespace Pay.Service.Cache {
    /// <summary>
    /// StackExchange.Redis管理者
    /// 注意：这个客户端没有连接池的概念，而是有了多路复用技术
    /// </summary>
    public class RedisHelper : ICache {
        /// <summary>
        /// 连接池
        /// </summary>
        private ConnectionMultiplexer _instance = null;
        private int db;

        public RedisHelper() {
            if (_instance == null || (_instance != null && _instance.IsConnected)) {
                var conn = ConfigurationManager.ConnectionStrings["CacheRedis"];
                var connarr = conn.ConnectionString.Split('@');
                db = int.Parse(conn.ProviderName);
                var pwd = connarr[0];
                var address = connarr[1];
                if (!string.IsNullOrEmpty(pwd)) address += ",password=" + pwd;
                address += ",defaultDatabase=" + db;
                var options = ConfigurationOptions.Parse(address);
                //options.ConnectTimeout = 1000;//连接操作超时（ms）
                //options.KeepAlive = 180;//发送消息以帮助保持套接字活动的时间（秒）（默认为60秒）
                //options.SyncTimeout = 2000;//时间（ms）允许进行同步操作
                //options.ConnectRetry = 3;//重试连接的次数
                _instance = ConnectionMultiplexer.Connect(address);
            }
            //注册如下事件
            _instance.ConnectionFailed += MuxerConnectionFailed;
            _instance.ConnectionRestored += MuxerConnectionRestored;
            _instance.ErrorMessage += MuxerErrorMessage;
            _instance.ConfigurationChanged += MuxerConfigurationChanged;
            _instance.HashSlotMoved += MuxerHashSlotMoved;
            _instance.InternalError += MuxerInternalError;
        }

        private const int _expirySeconds = 60;
        private JsonSerializerSettings jsonConfig = new JsonSerializerSettings() {
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            NullValueHandling = NullValueHandling.Ignore
        };

        public bool Set(string key, object value, int seconds = 0) {
            if (key == null) throw new ArgumentNullException("key");
            if (value != null) {
                if (seconds == 0) seconds = _expirySeconds;
                var dt = DateTime.Now;
                var timeSpan = dt.AddSeconds(seconds) - dt;
                var data = JsonConvert.SerializeObject(value, jsonConfig);
                //try {
                var database = _instance.GetDatabase();
                return database.StringSet(key, data, timeSpan);
                //} catch { }
            }
            return false;
        }

        private bool Set(IDatabase db, string key, object value, int seconds = 0) {
            if (key == null) throw new ArgumentNullException("key");
            if (value != null) {
                if (seconds == 0) seconds = _expirySeconds;
                var dt = DateTime.Now;
                var timeSpan = dt.AddSeconds(seconds) - dt;
                var data = JsonConvert.SerializeObject(value, jsonConfig);
                //try {
                return db.StringSet(key, data, timeSpan);
                //} catch { }
            }
            return false;
        }

        public T GetOrCreate<T>(string cacheKey, Func<T> create, int seconds = int.MaxValue) {
            var tags = cacheKey.Split(':');
            var tag = tags[0];
            var key = tags[1];
            RedisValue cacheValue = RedisValue.Null;
            IDatabase database = null;
            //try {
            database = _instance.GetDatabase();
            cacheValue = database.StringGet(tag);
            //} catch {
            //}
            if (!cacheValue.IsNull) {
                var joject = JsonConvert.DeserializeObject<JObject>(cacheValue, jsonConfig);
                JToken jtoken = null;
                if (joject.TryGetValue(key, out jtoken)) {
                    return jtoken.ToObject<T>();
                } else {
                    var result = create();
                    if (result == null) return result;
                    joject.Add(key, JToken.FromObject(result));
                    Set(database, tag, joject, seconds);
                    return result;
                }
            } else {
                var result = create();
                if (result == null) return result;
                var joject = new JObject();
                joject.Add(key, JToken.FromObject(result));
                Set(database, tag, joject, seconds);
                return result;
            }
        }

        public T Get<T>(string key) {
            if (key == null) throw new ArgumentNullException("key");

            var value = default(T);
            //try {
            var database = _instance.GetDatabase();
            var cacheValue = database.StringGet(key);
            if (!cacheValue.IsNull) {
                value = JsonConvert.DeserializeObject<T>(cacheValue, jsonConfig);
                return value;
            }
            //} catch { }
            return value;
        }

        public bool Remove(string key) {
            //try {
            var database = _instance.GetDatabase();
            return database.KeyDelete(key);
            //} catch { return false; }
        }

        public void Remove(IEnumerable<string> tags) {
            var count = tags.Count();
            if (count > 0) {
                RedisKey[] keys = new RedisKey[count];
                tags.ForEach((tag, i) => {
                    keys[i] = tag;
                });
                //try {
                var database = _instance.GetDatabase();
                database.KeyDelete(keys);
                //} catch { }
            }
        }

        public bool RemoveAll() {
            var server = _instance.GetServer(_instance.GetEndPoints()[0]);
            server.FlushDatabase(database: db);
            return true;
        }

        /// <summary>
        /// 配置更改时
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MuxerConfigurationChanged(object sender, EndPointEventArgs e) {
            IocHelper.Get<ILogHelper>("data_change").Info("Configuration changed: " + e.EndPoint);
        }
        /// <summary>
        /// 发生错误时
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MuxerErrorMessage(object sender, RedisErrorEventArgs e) {
            IocHelper.Get<ILogHelper>("data_change").Info("ErrorMessage: " + e.Message);
        }
        /// <summary>
        /// 重新建立连接之前的错误
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MuxerConnectionRestored(object sender, ConnectionFailedEventArgs e) {
            IocHelper.Get<ILogHelper>("data_change").Info("ConnectionRestored: " + e.EndPoint);
        }
        /// <summary>
        /// 连接失败 ， 如果重新连接成功你将不会收到这个通知
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MuxerConnectionFailed(object sender, ConnectionFailedEventArgs e) {
            IocHelper.Get<ILogHelper>("data_change").Info("重新连接：Endpoint failed: " + e.EndPoint + ", " + e.FailureType + (e.Exception == null ? "" : (", " + e.Exception.Message)));
        }
        /// <summary>
        /// 更改集群
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MuxerHashSlotMoved(object sender, HashSlotMovedEventArgs e) {
            IocHelper.Get<ILogHelper>("data_change").Info("HashSlotMoved:NewEndPoint" + e.NewEndPoint + ", OldEndPoint" + e.OldEndPoint);
        }
        /// <summary>
        /// redis类库错误
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MuxerInternalError(object sender, InternalErrorEventArgs e) {
            IocHelper.Get<ILogHelper>("data_change").Info("InternalError:Message" + e.Exception.Message);
        }

        private bool _disposed = false;
        public void _Dispose(bool disposable){
            if(!_disposed){
                _instance.Close();
                _instance.Dispose();
                _disposed = true;
            }
        }

        public void Dispose() {
            _Dispose(false);
        }
    }
}